/*
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 *
 * Clock.h
 * Provides the TimeInterval and TimeStamp classes.
 * Copyright (C) 2005-2012 Simon Newton
 *
 * The struct timeval can represent both absolute time and time intervals.
 * We define our own wrapper classes that:
 *   - hide some of the platform differences, like the fact windows doesn't
 *     provide timersub.
 *   - Reduces bugs by using the compiler to check if the value was supposed
 *     to be an interval or absolute time. For example, passing an absolute
 *     time intstead of an Interval to RegisterTimeout would be bad.
 */

#ifndef INCLUDE_OLA_CLOCK_H_
#define INCLUDE_OLA_CLOCK_H_

#if HAVE_CONFIG_H
#  include <config.h>
#endif

#include <ola/base/Macro.h>
#include <stdint.h>
#include <sys/time.h>

#include <iomanip>
#include <ostream>
#include <sstream>
#include <string>


namespace ola {

using std::ostream;

static const int USEC_IN_SECONDS = 1000000;
static const int ONE_THOUSAND = 1000;

/**
 * Don't use this class directly. It's an implementation detail of TimeInterval
 * and TimeStamp.
 */
class BaseTimeVal {
  public:
    // Constructors
    BaseTimeVal() { timerclear(&m_tv); }

    BaseTimeVal(int32_t sec, int32_t usec) {
      m_tv.tv_sec = sec;
      m_tv.tv_usec = usec;
    }

    explicit BaseTimeVal(const struct timeval &timestamp) { m_tv = timestamp; }
    explicit BaseTimeVal(int64_t interval_useconds) { Set(interval_useconds); }

    BaseTimeVal(const BaseTimeVal &other) : m_tv(other.m_tv) {}

    // Assignable
    BaseTimeVal& operator=(const BaseTimeVal& other) {
      if (this != &other) {
        m_tv = other.m_tv;
      }
      return *this;
    }

    BaseTimeVal& operator=(const struct timeval &tv) {
      m_tv = tv;
      return *this;
    }

    // Comparables
    bool operator==(const BaseTimeVal &other) const {
      return timercmp(&m_tv, &other.m_tv, ==);
    }

    bool operator!=(const BaseTimeVal &other) const {
      return !(*this == other);
    }

    bool operator>(const BaseTimeVal &other) const {
      return timercmp(&m_tv, &other.m_tv, >);
    }

    bool operator>=(const BaseTimeVal &other) const {
      return timercmp(&m_tv, &other.m_tv, >=);
    }

    bool operator<(const BaseTimeVal &other) const {
      return timercmp(&m_tv, &other.m_tv, <);
    }

    bool operator<=(const BaseTimeVal &other) const {
      return timercmp(&m_tv, &other.m_tv, <=);
    }

    // Arithmetic
    BaseTimeVal& operator+=(const BaseTimeVal& other) {
      if (this != &other) {
        timeradd(&m_tv, &other.m_tv, &m_tv);
      }
      return *this;
    }

    BaseTimeVal &operator-=(const BaseTimeVal &other) {
      if (this != &other) {
        TimerSub(m_tv, other.m_tv, &m_tv);
      }
      return *this;
    }

    const BaseTimeVal operator+(const BaseTimeVal &interval) const {
      BaseTimeVal result = *this;
      result += interval;
      return result;
    }

    const BaseTimeVal operator-(const BaseTimeVal &other) const {
      BaseTimeVal result;
      TimerSub(m_tv, other.m_tv, &result.m_tv);
      return result;
    }

    BaseTimeVal operator*(unsigned int i) const {
      int64_t as_int = (*this).AsInt();
      as_int *= i;
      return BaseTimeVal(as_int);
    }

    // Various other methods.
    bool IsSet() const {
      return timerisset(&m_tv);
    }

    void AsTimeval(struct timeval *tv) const {
      *tv = m_tv;
    }

    // Returns the seconds portion.
    time_t Seconds() const { return m_tv.tv_sec; }
    // Returns the microseconds portion
    suseconds_t MicroSeconds() const { return m_tv.tv_usec; }

    // Returns the entire BaseTimeVal as milliseconds
    int64_t InMilliSeconds() const {
      return (m_tv.tv_sec * static_cast<int64_t>(ONE_THOUSAND) +
              m_tv.tv_usec / ONE_THOUSAND);
    }

    // Returns the entire BaseTimeVal as microseconds.
    int64_t AsInt() const {
      return (m_tv.tv_sec * static_cast<int64_t>(USEC_IN_SECONDS) +
              m_tv.tv_usec);
    }

    std::string ToString() const {
      std::stringstream str;
      str << m_tv.tv_sec << "." << std::setfill('0') << std::setw(6)
          << m_tv.tv_usec;
      return str.str();
    }

  private:
    struct timeval m_tv;

    /**
     * We don't use timersub here because windows doesn't define it.
     */
    void TimerSub(const struct timeval &tv1, const struct timeval &tv2,
                  struct timeval *result) const {
      result->tv_sec = tv1.tv_sec - tv2.tv_sec;
      result->tv_usec = tv1.tv_usec - tv2.tv_usec;
      if (result->tv_usec < 0) {
          result->tv_sec--;
          result->tv_usec += USEC_IN_SECONDS;
      }
    }

    void Set(int64_t interval_useconds) {
#ifdef HAVE_TIME_T
      m_tv.tv_sec = static_cast<time_t>(
          interval_useconds / USEC_IN_SECONDS);
#else
      m_tv.tv_sec = interval_useconds / USEC_IN_SECONDS;
#endif

#ifdef HAVE_SUSECONDS_T
       m_tv.tv_usec = static_cast<suseconds_t>(
         interval_useconds % USEC_IN_SECONDS);
#else
      m_tv.tv_usec = interval_useconds % USEC_IN_SECONDS;
#endif
    }
};

/*
 * A time interval, with usecond accuracy.
 */
class TimeInterval {
  public:
    // Constructors
    TimeInterval() {}
    TimeInterval(int32_t sec, int32_t usec) : m_interval(sec, usec) {}
    explicit TimeInterval(int64_t usec) : m_interval(usec) {}

    TimeInterval(const TimeInterval &other) : m_interval(other.m_interval) {}

    // Assignable
    TimeInterval& operator=(const TimeInterval& other) {
      if (this != &other) {
        m_interval = other.m_interval;
      }
      return *this;
    }

    // Comparables
    bool operator==(const TimeInterval &other) const {
      return m_interval == other.m_interval;
    }

    bool operator!=(const TimeInterval &other) const {
      return m_interval != other.m_interval;
    }

    bool operator>(const TimeInterval &other) const {
      return m_interval > other.m_interval;
    }

    bool operator>=(const TimeInterval &other) const {
      return m_interval >= other.m_interval;
    }

    bool operator<(const TimeInterval &other) const {
      return m_interval < other.m_interval;
    }

    bool operator<=(const TimeInterval &other) const {
      return m_interval <= other.m_interval;
    }

    // Arithmetic
    TimeInterval& operator+=(const TimeInterval& other) {
      if (this != &other) {
        m_interval += other.m_interval;
      }
      return *this;
    }

    TimeInterval operator*(unsigned int i) const {
      return TimeInterval(m_interval * i);
    }

    // Various other methods.
    void AsTimeval(struct timeval *tv) const { m_interval.AsTimeval(tv); }

    time_t Seconds() const { return m_interval.Seconds(); }
    suseconds_t MicroSeconds() const { return m_interval.MicroSeconds(); }

    int64_t InMilliSeconds() const { return m_interval.InMilliSeconds(); }
    int64_t AsInt() const { return m_interval.AsInt(); }

    std::string ToString() const { return m_interval.ToString(); }

    friend ostream& operator<< (ostream &out, const TimeInterval &interval) {
      return out << interval.m_interval.ToString();
    }

  private:
    explicit TimeInterval(const BaseTimeVal &time_val) : m_interval(time_val) {}

    BaseTimeVal m_interval;
    friend class TimeStamp;
};


/*
 * Represents a point in time with usecond accuracy.
 */
class TimeStamp {
  public:
    // Constructors
    TimeStamp() {}
    explicit TimeStamp(const struct timeval &timestamp) : m_tv(timestamp) {}

    TimeStamp(const TimeStamp &other) : m_tv(other.m_tv) {}

    // Assignable
    TimeStamp& operator=(const TimeStamp& other) {
      if (this != &other) {
        m_tv = other.m_tv;
      }
      return *this;
    }

    TimeStamp& operator=(const struct timeval &tv) {
      m_tv = tv;
      return *this;
    }

    // Comparables
    bool operator==(const TimeStamp &other) const { return m_tv == other.m_tv; }
    bool operator!=(const TimeStamp &other) const { return m_tv != other.m_tv; }
    bool operator>(const TimeStamp &other) const { return m_tv > other.m_tv; }
    bool operator>=(const TimeStamp &other) const { return m_tv >= other.m_tv; }
    bool operator<(const TimeStamp &other) const { return m_tv < other.m_tv; }
    bool operator<=(const TimeStamp &other) const { return m_tv <= other.m_tv; }

    // Arithmetic
    TimeStamp &operator+=(const TimeInterval &interval) {
      m_tv += interval.m_interval;
      return *this;
    }

    TimeStamp &operator-=(const TimeInterval &interval) {
      m_tv -= interval.m_interval;
      return *this;
    }

    const TimeStamp operator+(const TimeInterval &interval) const {
      return TimeStamp(m_tv + interval.m_interval);
    }

    const TimeInterval operator-(const TimeStamp &other) const {
      return TimeInterval(m_tv - other.m_tv);
    }

    const TimeStamp operator-(const TimeInterval &interval) const {
      return TimeStamp(m_tv - interval.m_interval);
    }

    // Various other methods.
    bool IsSet() const { return m_tv.IsSet(); }

    time_t Seconds() const { return m_tv.Seconds(); }
    suseconds_t MicroSeconds() const { return m_tv.MicroSeconds(); }

    std::string ToString() const { return m_tv.ToString(); }

    friend ostream& operator<< (ostream &out, const TimeStamp &timestamp) {
      return out << timestamp.m_tv.ToString();
    }

  private:
    BaseTimeVal m_tv;

    explicit TimeStamp(const BaseTimeVal &time_val) : m_tv(time_val) {}
};


/*
 * Used to get the current time.
 */
class Clock {
  public:
    Clock() {}
    virtual ~Clock() {}
    virtual void CurrentTime(TimeStamp *timestamp) const {
      struct timeval tv;
      gettimeofday(&tv, NULL);
      *timestamp = tv;
    }

  private:
    DISALLOW_COPY_AND_ASSIGN(Clock);
};


/**
 * A Mock Clock used for testing.
 */
class MockClock: public Clock {
  public:
    MockClock() : Clock() {}

    // Advance the time
    void AdvanceTime(const TimeInterval &interval) {
      m_offset += interval;
    }

    void AdvanceTime(int32_t sec, int32_t usec) {
      TimeInterval interval(sec, usec);
      m_offset += interval;
    }

    void CurrentTime(TimeStamp *timestamp) const {
      struct timeval tv;
      gettimeofday(&tv, NULL);
      *timestamp = tv;
      *timestamp += m_offset;
    }
  private:
    TimeInterval m_offset;
};
}  // namespace ola
#endif  // INCLUDE_OLA_CLOCK_H_
